/*
 * Copyright (C) 2020 The Dagger Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package dagger.hilt.processor.internal.aggregateddeps;

import androidx.room3.compiler.processing.util.CompilationResultSubject;
import androidx.room3.compiler.processing.util.Source;
import dagger.hilt.android.testing.compile.HiltCompilerTests;
import dagger.hilt.processor.internal.GeneratedImport;
import java.util.function.Consumer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

/** Tests for errors generated by {@link AggregatedDepsProcessor} */
@RunWith(JUnit4.class)
public class AggregatedDepsProcessorErrorsTest {
  @Test
  public void reportMultipleAnnotationTypeKindErrors() {
    Source source =
        HiltCompilerTests.javaSource(
            "foo.bar.AnnotationsOnWrongTypeKind",
            "package foo.bar;",
            "",
            "import dagger.hilt.EntryPoint;",
            "import dagger.hilt.InstallIn;",
            "import dagger.Module;",
            "import dagger.hilt.components.SingletonComponent;",
            "import dagger.hilt.internal.ComponentEntryPoint;",
            "import dagger.hilt.internal.GeneratedEntryPoint;",
            "",
            "@InstallIn(SingletonComponent.class)",
            "@Module",
            "enum FooModule { VALUE }",
            "",
            "@InstallIn(SingletonComponent.class)",
            "@EntryPoint",
            "final class BarEntryPoint {}",
            "",
            "@InstallIn(SingletonComponent.class)",
            "@ComponentEntryPoint",
            "final class BazComponentEntryPoint {}",
            "",
            "@EntryPoint",
            "interface QuxEntryPoint {}",
            "",
            "@EntryPoint",
            "@Module",
            "interface DontMix{}",
            "");

    compile(
        source,
        subject -> {
          subject.compilationDidFail();
          subject
              .hasErrorContaining("Only classes and interfaces can be annotated with @Module")
              .onSource(source)
              .onLine(12);
          subject
              .hasErrorContaining("Only interfaces can be annotated with @EntryPoint")
              .onSource(source)
              .onLine(16);
          subject
              .hasErrorContaining("Only interfaces can be annotated with @ComponentEntryPoint")
              .onSource(source)
              .onLine(20);
          subject
              .hasErrorContaining(
                  "@EntryPoint foo.bar.QuxEntryPoint must also be annotated with @InstallIn")
              .onSource(source)
              .onLine(23);
          subject
              .hasErrorContaining(
                  "@Module and @EntryPoint cannot be used on the same interface")
              .onSource(source)
              .onLine(27);
        });
  }

  @Test
  public void testInvalidComponentInInstallInAnnotation() {
    Source module =
        HiltCompilerTests.javaSource(
            "test.FooModule",
            "package test;",
            "",
            "import dagger.Module;",
            "import dagger.hilt.InstallIn;",
            "import dagger.hilt.android.qualifiers.ApplicationContext;",
            "",
            "@InstallIn(ApplicationContext.class)", // Error: Not a Hilt component
            "@Module",
            "final class FooModule {}");

    compile(
        module,
        subject -> {
          subject.compilationDidFail();
          subject
              .hasErrorContaining(
                  "@InstallIn, can only be used with @DefineComponent-annotated classes, but"
                      + " found: [dagger.hilt.android.qualifiers.ApplicationContext]")
              .onSource(module)
              .onLine(9);
        });
  }

  @Test
  public void testMissingInstallInAnnotation() {
    Source source =
        HiltCompilerTests.javaSource(
            "foo.bar.AnnotationsOnWrongTypeKind",
            "package foo.bar;",
            "",
            "import dagger.Module;",
            "",
            "@Module", // Error: Doesn't have InstallIn annotation
            "final class FooModule {}");

    compile(
        source,
        subject -> {
          subject.compilationDidFail();
          subject
              .hasErrorContaining("foo.bar.FooModule is missing an @InstallIn annotation")
              .onSource(source)
              .onLine(6);
        });
  }

  @Test
  public void testNoErrorOnDaggerGeneratedModules() {
    Source source =
        HiltCompilerTests.javaSource(
            "foo.bar.BarModule",
            "package foo.bar;",
            "",
            GeneratedImport.IMPORT_GENERATED_ANNOTATION,
            "import dagger.Module;",
            "",
            "@Module",
            "@Generated(value = \"something\")", // Error: Isn't Dagger-generated but missing
            // InstallIn
            "final class FooModule {}",
            "",
            "@Module",
            "@Generated(value = \"dagger\")", // No error because the module is dagger generated
            "final class BarModule {}");

    compile(
        source,
        subject -> {
          subject.compilationDidFail();
          subject.hasErrorCount(1);
          subject
              .hasErrorContaining("foo.bar.FooModule is missing an @InstallIn annotation")
              .onSource(source)
              .onLine(8);
        });
  }

  @Test
  public void testModuleWithOnlyParamConstructor_fails() {
    Source source =
        HiltCompilerTests.javaSource(
            "foo.bar.FooModule",
            "package foo.bar;",
            "",
            "import dagger.Module;",
            "import dagger.Provides;",
            "import dagger.hilt.InstallIn;",
            "import dagger.hilt.components.SingletonComponent;",
            "",
            "@Module",
            "@InstallIn(SingletonComponent.class)",
            "final class FooModule {",
            "  FooModule(String arg) {}",
            "",
            "  @Provides",
            "  String provideString() {",
            "    return \"\";",
            "  }",
            "}");

    compile(
        source,
        subject -> {
          subject.compilationDidFail();
          subject.hasErrorCount(1);
          subject.hasErrorContaining(
              "Modules that need to be instantiated by Hilt must have a visible, empty"
                  + " constructor.");
        });
  }

  @Test
  public void testInnerModule() {
    Source source =
        HiltCompilerTests.javaSource(
            "foo.bar.Outer",
            "package foo.bar;",
            "",
            "import dagger.Module;",
            "import dagger.hilt.InstallIn;",
            "import dagger.hilt.components.SingletonComponent;",
            "",
            "final class Outer {",
            "  @Module",
            "  @InstallIn(SingletonComponent.class)",
            "  final class InnerModule {}",
            "}");

    compile(
        source,
        subject -> {
          subject.compilationDidFail();
          subject.hasErrorCount(1);
          subject.hasErrorContaining(
              "Nested @InstallIn modules must be static unless they are directly nested within"
                  + " a test. Found: foo.bar.Outer.InnerModule");
        });
  }

  @Test
  public void testInnerModuleInTest() {
    Source source =
        HiltCompilerTests.javaSource(
            "foo.bar.Outer",
            "package foo.bar;",
            "",
            "import dagger.Module;",
            "import dagger.hilt.InstallIn;",
            "import dagger.hilt.components.SingletonComponent;",
            "import dagger.hilt.android.testing.HiltAndroidTest;",
            "",
            "@HiltAndroidTest",
            "final class Outer {",
            "  static class Nested {",
            "    @Module",
            "    @InstallIn(SingletonComponent.class)",
            "    final class InnerModule {}",
            "  }",
            "}");

    compile(
        source,
        subject -> {
          subject.compilationDidFail();
          subject.hasErrorCount(1);
          subject.hasErrorContaining(
              "Nested @InstallIn modules must be static unless they are directly nested within"
                  + " a test. Found: foo.bar.Outer.Nested.InnerModule");
        });
  }

  @Test
  public void testInnerModuleInTest_succeeds() {
    Source source =
        HiltCompilerTests.javaSource(
            "foo.bar.Outer",
            "package foo.bar;",
            "",
            "import dagger.Module;",
            "import dagger.hilt.InstallIn;",
            "import dagger.hilt.components.SingletonComponent;",
            "import dagger.hilt.android.testing.HiltAndroidTest;",
            "",
            "@HiltAndroidTest",
            "public final class Outer {",
            "  @Module",
            "  @InstallIn(SingletonComponent.class)",
            "  static final class InnerModule {}",
            "}");

    compile(source, subject -> subject.hasErrorCount(0));
  }

  private static void compile(
      Source source, Consumer<CompilationResultSubject> onCompilationResult) {
    HiltCompilerTests.hiltCompiler(source).compile(onCompilationResult);
  }
}
